#include "Disk.h"
#include <stdio.h>

void Disk::initMembers() {
	if (pthread_mutex_init(&m_blocksMutex, NULL) != 0 ||
		pthread_mutex_init(&m_inodesMutex, NULL) != 0)
		{
			perror("disk mutexes initialization");
			exit(1);
		}
	disk = new byte[blockSize * numBlocks];
	superblock  = (SuperBlock*) (disk + 0);
	free_blocks = (int*)(disk + blockSize);
  	free_inodes = (int*)(disk + 2*blockSize);
  	inodes_array = (Inode*)(disk + 3*blockSize);

}

//load from file
Disk::Disk(){
	FILE* sysfile = fopen ("SYS_FILE","rb");

	SuperBlock temp;
	fread(&temp, sizeof(SuperBlock), 1, sysfile);
	fclose(sysfile);
	blockSize = temp.block_size;
	numBlocks = temp.num_blocks;

	initMembers();

  	loadFromFile();


  	cout << "Disk loaded from SYS_FILE."<<endl;
}

//create a new SYS_FILE
Disk::Disk(int bSize, int numberOfInodes, int diskSize)
{
	  blockSize = bSize;
  	  numBlocks = diskSize / blockSize;

  	  initMembers();
	  //cout << "SYS_FILE not found - creating a new one..."<<endl;
	  //General Data:
	  superblock->num_blocks=numBlocks;
	  superblock->block_size=blockSize;
	  //Blocks Control:

	  //init list:
	  superblock->num_free_blocks=0;
	  superblock->index_of_first_free_block=blockSize/4 - 1;
	  free_blocks[blockSize/4 - 1]=-1; //no next
	  for(unsigned int i=numBlocks-1; i>((numberOfInodes * sizeof(Inode))/blockSize)+3; i--) {
		  freeBlock(i);
	  }

	  //Inode Control
	  superblock->size_inodes_array = numberOfInodes;// * sizeof(Inode);

	  //init list:
	  superblock->num_free_inodes=0;
	  superblock->index_of_first_free_inode=blockSize/4 - 1;
	  free_inodes[blockSize/4 - 1]=-1; //no next
	  for(int i=numberOfInodes-1; i>=0; i--) {
		  freeInode(i);
	  }

	  int root = allocateInode();
	  setInodeType(root, DIRECTORY);
	  setFileSize(root, 0);
	  superblock->root_inode = root;

	 cout << "A new SYS_FILE has been created! root="<<root<<endl;
	 dumpToFile();
	 // cout << "Disk has been initialized: "<<superblock->num_blocks<<" blocks."<<endl;
}

Disk::~Disk()
{
	// cout << "shutting down - saving disk.."<<endl;
	dumpToFile();
	delete []disk;
	cout << "Disk is closed."<<endl;
}

void Disk::dumpToFile(){
	FILE* sysfile = fopen ("SYS_FILE","wb");
	fwrite(disk, blockSize, numBlocks, sysfile);
	fclose (sysfile);
}

void Disk::loadFromFile(){
	FILE* sysfile = fopen ("SYS_FILE","rb");
  	fread(disk, blockSize, numBlocks, sysfile);
	fclose (sysfile);
}

// – allocates a new i-node and returns its number
int Disk::allocateInode(){
	// cout << "allocateInode" << endl;
	int inode;
	pthread_mutex_lock(&m_inodesMutex);

	if(superblock->index_of_first_free_inode < blockSize/4 - 1) {
		superblock->num_free_inodes--;
		inode = free_inodes[superblock->index_of_first_free_inode++];
	}
	else {
	//else: superblock.index_of_first_free_block points to the next node.
		int nextNode=free_inodes[superblock->index_of_first_free_inode];
		if(nextNode == -1) {
			cout << " Disk::allocateInode cant allocate inode - no free inodes :("<<endl;
			pthread_mutex_unlock(&m_inodesMutex);
			return -1;
		}
		// cout<<"allocateInode: first node has no free stuff - moving to next node."<<endl;
		readBlock(nextNode, (byte*)free_inodes);
		freeBlock(nextNode);
		superblock->num_free_inodes--;
		superblock->index_of_first_free_inode=1;
		inode = free_inodes[0];
	}

	//initialize the inode:
	for(int i=0; i<10; i++) {
		inodes_array[inode].direct_block[i]= -1;
	}
	inodes_array[inode].indirect_block=-1;
	inodes_array[inode].num_hard_links=1;

	pthread_mutex_unlock(&m_inodesMutex);
	return inode;
}


//–frees the specified i-node and also frees all the data blocks pointed to by it
//(directly or indirectly).
int Disk::freeInode(int inode){
	// cout << "freeInode " << inode<<endl;

	if(inode<0 || inode>=superblock->size_inodes_array) {
		cout<< "Disk:: freeInode: bad inode number" << inode << endl;
		return 1;
	}

	//todo: move to FS?
	if(inodes_array[inode].indirect_block>0) {
		freeBlock(inodes_array[inode].indirect_block);
	}
	pthread_mutex_lock(&m_inodesMutex);
	//can i use the first node?
	if(superblock->index_of_first_free_inode > 0) {
		superblock->num_free_inodes++;
		free_inodes[--superblock->index_of_first_free_inode]=inode;
		pthread_mutex_unlock(&m_inodesMutex);
		return 0;
	}

	// cout << "first list node is full : make room by saving first_node into another block"<<endl;
	int nextNode=allocateDataBlock();
	writeBlock(nextNode, (byte*)free_inodes);
	free_inodes[blockSize/4 - 1]=nextNode;
	free_inodes[blockSize/4 - 2]=inode;
	superblock->index_of_first_free_inode=blockSize/4 - 2;
	superblock->num_free_inodes++;

	pthread_mutex_unlock(&m_inodesMutex);
	return 0;
}


// – allocates a data block and returns its number
int Disk::allocateDataBlock() {
	//return listAllocate(free_blocks, superblock->index_of_first_free_block, superblock->num_free_blocks);
	// cout << "allocateDataBlock" << endl;
	pthread_mutex_lock(&m_blocksMutex);
	//cout <<"ding "<< superblock->index_of_first_free_block << endl;
	if(superblock->index_of_first_free_block < blockSize/4 - 1) {
		superblock->num_free_blocks--;
		int ans = free_blocks[superblock->index_of_first_free_block++];
		pthread_mutex_unlock(&m_blocksMutex);
		return ans;
	}
	//cout <<"ping "<< superblock->index_of_first_free_block << endl;

	//else: superblock.index_of_first_free_block points to the next node.
	int ans=free_blocks[superblock->index_of_first_free_block];

	//cout <<"pong "<<endl;
	if(ans == -1) {
		cout << "Disk::allocateDataBlock: no more free blocks :("<<endl;
		pthread_mutex_unlock(&m_blocksMutex);
		return -1;
	}

	// cout<<"allocateDataBlock: first node has no free stuff - moving to next node."<<endl;
	readBlock(ans, (byte*)free_blocks);
	superblock->index_of_first_free_block=0;

	pthread_mutex_unlock(&m_blocksMutex);
	return ans;
}

// – frees the specified block.
int Disk::freeBlock(int block) {
	//return listFree(free_blocks, superblock->index_of_first_free_block, superblock->num_free_blocks, block);
	// cout << "freeBlock " << block<<endl;
	if(block<3 || block>=numBlocks) {
		// cout<< "freeDataBlock: bad block number!" << endl;
		return 1;
	}
	pthread_mutex_lock(&m_blocksMutex);

	//can i use the first node?
	if(superblock->index_of_first_free_block > 0) {
		superblock->num_free_blocks++;
		free_blocks[--superblock->index_of_first_free_block]=block;
		pthread_mutex_unlock(&m_blocksMutex);
		return 0;
	}

	// cout << "first node is full : make room by saving first_node into another block"<<endl;
	writeBlock(block, (byte*)free_blocks);
	free_blocks[blockSize/4 - 1]=block;
	superblock->index_of_first_free_block=blockSize/4 - 1;

	pthread_mutex_unlock(&m_blocksMutex);
	return 0;
}

//Use (and update) the maps in the super block in these allocate/free methods.

// – return the type of the file (normal/directory/soft-link).
int Disk::getInodeType(int inode) {
	if(inode<0 || inode>=superblock->size_inodes_array) {
		// cout << "getInodeType: invalid inode number!" << endl;
		return -1;
	}
	return inodes_array[inode].file_type;
}

// – set the type of the file (normal/directory/soft-link).
int Disk::setInodeType(int inode, int filetype) {
	if(inode<0 || inode>=superblock->size_inodes_array) {
		// cout << "getInodeType: invalid inode number!" << endl;
		return -1;
	}
	inodes_array[inode].file_type=filetype;
	return 0;
}

// – return the number of the i-th data block containing the data of this file
//(for 0<=i<=9 this is just stored in the 10 pointers in this i-node, for i>9,
//you must retrieve it from the indirect data block..).
int Disk::getDataBlock (int inode, int i) {
	if(inode<0 || inode>=superblock->size_inodes_array) {
		cout << "Disk::getDataBlock: invalid inode number " << inode << endl;
		return -1;
	}

	if(i >= 10 + (blockSize / 4) || i < 0) {
		cout << "Disk::getDataBlock ERROR: block number "<< i <<" is not possible in this Disk!" <<endl;
		return -1;
	}

	if( i*blockSize > inodes_array[inode].file_size ) {
		//cout << "Disk::getDataBlock block number "<< i <<" does not exist in this File!" <<endl;
		return -1;
	}

	//todo: check if i is too big for file size
	if(i<10) {
		// cout << "getDataBlock returns direct_block "<<i<<" for node "<<inode<< ": "<<inodes_array[inode].direct_block[i]<<endl;
		return inodes_array[inode].direct_block[i];
	}
	if(inodes_array[inode].indirect_block < 0) {
		//cout << "Disk::getDataBlock: inode " << inode << " has no indirect_block!"<< endl;
		return -1;
	}
	int ind[blockSize / 4];
	readBlock(inodes_array[inode].indirect_block, (byte *)ind);
	return ind[i-10];
}

// – sets the i-th data block of this file to be dblockNum.
int Disk::setDataBlock (int inode, int i, int dblockNum ) {
	// cout << "setDataBlock: inode=" <<inode << ", i="<<i<<", block="<<dblockNum<< endl;
	if(i >= 10 + (blockSize / 4)  || i < 0) {
		cout << "Disk::setDataBlock block number "<< i <<" is not possible in this Disk!" <<endl;
		return -1;
	}

	if(inode<0 || inode>=superblock->size_inodes_array) {
		cout << "Disk::setDataBlock: invalid inode number " << inode << endl;
		return -1;
	}
	if(dblockNum>=superblock->num_blocks) {
		cout << "Disk::setDataBlock: invalid block number "<< dblockNum << endl;
		return -1;
	}

	if(i<10) {
		// cout << "setDataBlock successfull: inode=" <<inode << ", i="<<i<<", block="<<dblockNum<< endl;
		inodes_array[inode].direct_block[i]=dblockNum;
		return 0;
	}

	//cout << "Disk::setDataBlock once you go indirect you never go back"<<endl;

	if(inodes_array[inode].indirect_block==-1) {
		inodes_array[inode].indirect_block=allocateDataBlock();
		if(inodes_array[inode].indirect_block < 0) {
			cout << "Disk::setDataBlock couldn't allocate an indirect inode!" <<endl;
			return -1;
		}
	}

	int ind[blockSize / 4];
	readBlock(inodes_array[inode].indirect_block, (byte *)ind);
	ind[i-10]=dblockNum;
	writeBlock(inodes_array[inode].indirect_block, (byte *)ind);

	//alocate
	return 0;
}


// – return the file size saved in the given i-node.
//This is the exact size of the file in bytes, not the number of blocks used by this file.
int Disk::getFileSize(int inode) {
	if(inode<0 || inode>=superblock->size_inodes_array) {
		cout << "Disk::getInodeType: invalid inode number!" << endl;
		return -1;
	}
	return inodes_array[inode].file_size;
}

// set the file size.
int Disk::setFileSize(int inode, int newSize) {
	if(inode<0 || inode>=superblock->size_inodes_array) {
		cout << "Disk::getInodeType: invalid inode number!" << endl;
		return -1;
	}
	inodes_array[inode].file_size = newSize;

	return 0;
}

// read the entire content of the dblockNum's and copy it into buf.
int Disk::readBlock(int block, byte* buf, int offset, size_t n) {
	// cout << "readBlock "<<block <<endl;
	if(block<0 || block>=numBlocks) {
		cout<< "Disk::readBlock: bad block number "<< block << endl;
		return -1;
	}
	if(buf==NULL)	{
		cout<< "Disk::readBlock: bad data pointer!" << endl;
		return -1;
	}
	if(n<=0 || n>blockSize) {
		cout<< "Disk::readBlock: bad data size!" << endl;
		return -1;
	}

	memcpy(buf, disk + block*blockSize + offset, n);

	return 0;
}

int Disk::readBlock(int block, byte* buf) {
	return readBlock(block, buf, 0, blockSize);
}

//overwrite the content of block with number dblockNum with the newdata block.
//Assume newdata's length is equal to dataBlockSize.
int Disk::writeBlock(int block, const byte* newdata, int offset, size_t n){
	// cout << "writeBlock block="<<block <<" n="<<n<<endl;
	if(block<0 || block>=numBlocks) {
		// cout<< "bad block number!" << endl;
		return -1;
	}
	if(newdata==NULL)	{
		// cout << "bad data pointer!" << endl;
		return -1;
	}
	if(n<=0 || n>blockSize) {
		// cout<< "bad data size!" << endl;
		return -1;
	}

	memcpy(disk + block*blockSize + offset, newdata, n);
	// cout << "Write successfull"<<endl;
	return 0;
}

int Disk::writeBlock(int block, const byte* newdata) {
	return writeBlock(block, newdata, 0, blockSize);
}

int Disk::numFreeBlocks(){
	return superblock->num_free_blocks;
}

int Disk::numFreeInodes(){
	return superblock->num_free_inodes;
}

int Disk::getBlockSize() {
	return blockSize;
}

int Disk::decNumHardLinks(int inode) {
	return --inodes_array[inode].num_hard_links;
}

int Disk::incNumHardLinks(int inode) {
	return ++inodes_array[inode].num_hard_links;
}

int Disk::getNumHardLinks(int inode) {
	return inodes_array[inode].num_hard_links;
}


int Disk::status(){
	cout << "Disk status:"<<endl
	<< "block_size = " << superblock->block_size << " bytes." << endl
	<< "used blocks: " << (superblock->num_blocks-superblock->num_free_blocks) << " out of "<< superblock->num_blocks << endl
	<< "used inodes: " << (superblock->size_inodes_array - superblock->num_free_inodes) << " out of "<< superblock->size_inodes_array <<endl
	<< "inodes arrays takes up "<<sizeof(Inode) * superblock->size_inodes_array << " blocks."<<endl
	<< "used disk space: "<< (numBlocks - superblock->num_free_blocks) * blockSize << " out of "<< blockSize * numBlocks<<" bytes."<<endl;
	return 0;
}

bool test_Disk(){
//	Disk d(100, 2, 4000);
//	cout << "i have "<< d.numFreeInodes() <<" free inodes."<< endl;
//	for(int i=0; i<60; i++) {
//		int b=d.allocateInode();
//		cout << i << ": got inode " << b << endl;
//	}
//	for(int i=0; i<10; i++) {
//		d.freeInode(i);
//	}
//	for(int i=0; i<20; i++) {
//		int b=d.allocateInode();
//		cout << i << ": got inodes " << b << endl;
//	}
//	cout << "i have "<< d.numFreeInodes() <<" free inodes."<< endl;
	return true;
}
